# MinMaxScaler

import mglearn
from matplotlib import pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.svm import SVC

cancer = load_breast_cancer()
# 下面将核 SVM（SVC）应用在 cancer 数据集上，并使用MinMaxScaler 来预处理数据
X_train,X_test,y_train,y_test = train_test_split(cancer.data,cancer.target,random_state=1)
print(X_train.shape,y_train.shape)

scaler = MinMaxScaler()

'''
使用 fit 方法拟合 MinMaxScaler，并将其应用于训练数据。fit 方法会计算训练数据中每个特征的最大值和最小值。
与分类器和回归器不同，缩放器的 fit 方法只需要训练数据 X_train，而不需要目标变量 y_train，
因为缩放器只关注特征的范围，不涉及特征与目标变量的关系。
'''
scaler.fit(X_train)

'''
为了应用刚刚学习的变换（即对训练数据进行实际缩放），我们使用缩放器的 transform方法。
在 scikit-learn 中，每当模型返回数据的一种新表示时，都可以使用 transform方法：
'''

# 变换数据
X_train_scaled = scaler.transform(X_train)
# 在缩放之前和之后分别打印数据集属性
print("transformed shape: {}".format(X_train_scaled.shape))
print("per-feature minimum before scaling:\n {}".format(X_train.min(axis=0)))
print("per-feature maximum before scaling:\n {}".format(X_train.max(axis=0)))
print("per-feature minimum after scaling:\n {}".format(
 X_train_scaled.min(axis=0)))
print("per-feature maximum after scaling:\n {}".format(
 X_train_scaled.max(axis=0)))

'''
变换后的数据形状与原始数据相同，特征只是发生了移动和缩放。你可以看到，现在所有特征都位于 0 到 1 之间，这也符合我们的预期。
'''

# 为了将 SVM 应用到缩放后的数据上，还需要对测试集进行变换。这可以通过对 X_test 调用 transform 方法来完成

# 对测试数据进行变换
X_test_scaled = scaler.transform(X_test)
# 在缩放之后打印测试数据的属性
print("per-feature minimum after scaling:\n{}".format(X_test_scaled.min(axis=0)))
print("per-feature maximum after scaling:\n{}".format(X_test_scaled.max(axis=0)))

'''
测试集缩放后的值可能不在 0 到 1 之间，甚至超出这个范围。
这是因为 MinMaxScaler 对训练集和测试集应用相同的变换：减去训练集的最小值，再除以训练集的范围。
而测试集的最小值和范围可能与训练集不同。
'''

# 对训练数据和测试数据进行相同的缩放
# 为了让监督模型能够在测试集上运行，对训练集和测试集应用完全相同的变换是很重要的。
# 如果我们使用测试集的最小值和范围，下面这个例子展示了会发生什么

from sklearn.datasets import make_blobs

# 构造数据
X, _ = make_blobs(n_samples=50, centers=5, random_state=4, cluster_std=2)
# 将其分为训练集和测试集
X_train, X_test = train_test_split(X, random_state=5, test_size=.1)
# 绘制训练集和测试集
fig, axes = plt.subplots(1, 3, figsize=(13, 4))
axes[0].scatter(X_train[:, 0], X_train[:, 1],
 c=mglearn.cm2(0), label="Training set", s=60)
axes[0].scatter(X_test[:, 0], X_test[:, 1], marker='^',
 c=mglearn.cm2(1), label="Test set", s=60)
axes[0].legend(loc='upper left')
axes[0].set_title("Original Data")
# 利用MinMaxScaler缩放数据
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 将正确缩放的数据可视化
axes[1].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1],
 c=mglearn.cm2(0), label="Training set", s=60)
axes[1].scatter(X_test_scaled[:, 0], X_test_scaled[:, 1], marker='^',
 c=mglearn.cm2(1), label="Test set", s=60)
axes[1].set_title("Scaled Data")
# 单独对测试集进行缩放
# 使得测试集的最小值为0，最大值为1
# 千万不要这么做！这里只是为了举例
test_scaler = MinMaxScaler()
test_scaler.fit(X_test)
X_test_scaled_badly = test_scaler.transform(X_test)
# 将错误缩放的数据可视化
axes[2].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1],
 c=mglearn.cm2(0), label="training set", s=60)
axes[2].scatter(X_test_scaled_badly[:, 0], X_test_scaled_badly[:, 1],
 marker='^', c=mglearn.cm2(1), label="test set", s=60)
axes[2].set_title("Improperly Scaled Data")
for ax in axes:
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")

# plt.show()

'''
总结：
    未缩放的数据集：数据未经处理，特征值范围可能超出 0 到 1。
    正确缩放的数据集：用 MinMaxScaler 的 fit 方法拟合训练集，计算最小值和最大值，然后用 transform 方法对训练集和测试集进行缩放。
        所有特征值被缩放到 0 到 1 之间，数据的相对关系保持不变。
    错误缩放的数据集：分别对训练集和测试集调用 fit 和 transform，导致每个数据集的特征值被缩放到 0 到 1，但使用了不同的缩放参数，
        破坏了数据的相对关系，可能导致模型在测试集上表现不佳。

结论：正确的做法是只对训练集调用 fit，然后用相同的参数对训练集和测试集进行 transform，以保持数据的相对关系一致。

'''

'''
Tips:
快捷方式与高效的替代方法
通常来说，你想要在某个数据集上 fit 一个模型，然后再将其 transform。这是一个非
常常见的任务，通常可以用比先调用 fit 再调用 transform 更高效的方法来计算。对
于这种使用场景，所有具有 transform 方法的模型也都具有一个 fit_transform 方法。
下面是使用 StandardScaler 的一个例子：

In[9]:
    from sklearn.preprocessing import StandardScaler 
    scaler = StandardScaler() 
    # 依次调用fit和transform（使用方法链）
    X_scaled = scaler.fit(X).transform(X) 
    # 结果相同，但计算更加高效
    X_scaled_d = scaler.fit_transform(X)
虽然 fit_transform 不一定对所有模型都更加高效，但在尝试变换训练集时，使用这
一方法仍然是很好的做法。

'''

# 下面使用MinMaxScaler 对数据进行缩放，然后再拟合 SVC
X_train,X_test,y_train,y_test = train_test_split(cancer.data,cancer.target,random_state=0)
svm = SVC(C=100)
svm.fit(X_train,y_train)
print("Test set accuracy: {:.2f}".format(svm.score(X_test, y_test)))

# 使用0-1缩放进行预处理
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 在缩放后的训练数据上学习SVM
svm.fit(X_train_scaled,y_train)
# 在缩放后的测试集上计算分数
print("Scaled test set accuracy: {:.2f}".format(
 svm.score(X_test_scaled, y_test)))
# Test set accuracy: 0.94
# Scaled test set accuracy: 0.97